﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Text.RegularExpressions;
using TableCore.Exceptions;

namespace TableCore
{
    [Flags]
    public enum EClassFlag
    {
        None = 0,
        INDEX = 1,
        AUTO_ID = 2,
    }

    //public struct Variant : IEquatable<Variant>
    //{
    //    public string name;
    //    public string comment;
    //    public Variant(string name, string comment)
    //    {
    //        this.name = name;
    //        this.comment = comment;
    //    }

    //    public override int GetHashCode()
    //    {
    //        return name == null ? 0 : name.GetHashCode();
    //    }
    //    public override bool Equals(object obj)
    //    {
    //        return obj is Variant ? ((Variant)obj).name == name : false;
    //    }

    //    public bool Equals(Variant other)
    //    {
    //        return name == other.name;
    //    }

    //    public static bool operator ==(Variant a, Variant b)
    //    {
    //        return a.name == b.name;
    //    }

    //    public static bool operator !=(Variant a, Variant b)
    //    {
    //        return a.name != b.name;
    //    }
    //}

    public class ClassModel
    {
        public static Regex PROPERTY_PATTERN = new Regex(@"^[a-zA-Z0-9_]+(.[a-zA-Z]+)?$");
        public class Property
        {
            string mPName;
            public string Name
            {
                get { return mPName; }
                private set
                {
                    IsID = Utils.EqualIgnoreCase(value, "id");
                    if (IsID)
                        mPName = "id";
                    else
                        mPName = value;
                }
            }
            public GTType GenType { get; private set; }
            public bool IsID { get; private set; }
            public string Comment { get; set; }
            public int Index { get; internal set; }
            bool mIgnore;
            HashSet<string> mVariants;
            public bool Ignore
            {
                get { return (mIgnore && !IsID) || string.IsNullOrEmpty(Name); }
                set
                {
                    mIgnore = value;
                }
            }
            public bool HasVariants { get { return mVariants != null && mVariants.Count > 0; } }

            public Property()
            {

            }

            public Property(string name, GTType type)
            {
                Name = name;
                GenType = type;
                Ignore = false;
            }

            public void GetVariants(ICollection<string> variants)
            {
                if (mVariants != null && variants != null)
                {
                    foreach (var v in mVariants)
                    {
                        variants.Add(v);
                    }
                }
            }

            //public bool HasVariant(string variant)
            //{
            //    return mVariants != null && mVariants.Contains(variant);
            //}

            public void AddVariant(string variant)
            {
                if (string.IsNullOrEmpty(variant))
                    return;
                if (mVariants == null)
                    mVariants = new HashSet<string>();
                mVariants.Add(variant);
            }

            public override string ToString()
            {
                StringBuilder builder = new StringBuilder();
                builder.Append(Name);
                while (builder.Length < 20)
                    builder.Append(' ');
                builder.Append(":").Append(GenType.Name);
                return builder.ToString();
            }
        }

        public string ClassName { get; private set; }
        Property[] m_Properties;
        public bool IsIdDefined { get; private set; }
        public int IdIndex { get; private set; }
        public EClassFlag Flags { get; private set; }
        public bool AutoId { get { return (Flags & EClassFlag.AUTO_ID) != 0; } }
        public bool Indexed { get { return (Flags & EClassFlag.INDEX) != 0; } }

        public ClassModel(string className)
        {
            ClassName = className;
        }

        public ClassModel(string classname, Property[] propreties)
        {
            ClassName = classname;
            m_Properties = propreties;
            IsIdDefined = false;
            for (int i = 0; i < m_Properties.Length; i++)
            {
                m_Properties[i].Index = i;
                if (m_Properties[i].IsID)
                {
                    Flags &= ~EClassFlag.AUTO_ID;
                    IsIdDefined = true;
                    IdIndex = i;
                }
            }
        }

        public void GetProperties(IList<GTFile.ValueFilter> filters, DataTable table, int startRow, int startCol)
        {
            int colIndex = startCol;
            List<Property> lst = new List<Property>();
            var maxCol = table.Columns.Count;
            var maxRow = table.Rows.Count;
            if (maxRow <= startRow + 2)
                throw new Exception(string.Format("Sheet[\"{0}\"]表头数据定义缺失", table.TableName));
            Flags = 0;
            if (startCol > 0)
            {
                var flags = Utils.GetCell(table, 2, startCol - 1);
                if (!string.IsNullOrEmpty(flags))
                {
                    if (flags.Contains("INDEX"))
                        Flags |= EClassFlag.INDEX;
                    if (flags.Contains("AUTO_ID"))
                        Flags |= EClassFlag.AUTO_ID;
                }
            }
            while (colIndex < maxCol)
            {
                var cols = colIndex++;
                var cell = Utils.GetCell(table, startRow, cols);
                if (string.IsNullOrEmpty(cell))
                    break;
                if (!PROPERTY_PATTERN.IsMatch(cell))
                    continue;
                var typeName = Utils.GetCell(table, startRow + 1, cols);
                if (string.IsNullOrEmpty(typeName))
                    continue;
                if (!StringUtil.EqualIgnoreCase(cell, "id") && !GTFile.ValueFilter.PassValue(cell, filters))
                    continue;
                var type = Factory.GetInstance().GetGenType(typeName);
                var index = cell.LastIndexOf('.');
                string variant = null;
                if (index > 0)
                {
                    variant = cell.Substring(index + 1);
                    cell = cell.Substring(0, index);
                }
                Property p = null;
                for (int i = 0; i < lst.Count; i++)
                {
                    if (lst[i].Name == cell)
                    {
                        p = lst[i];
                        if (p.GenType != type)
                            throw new DifferentVariantTypeException(cell);
                        break;
                    }
                }
                if (p == null)
                {
                    p = new Property(cell, type);
                    p.Comment = Utils.GetCell(table, startRow + 2, cols);
                    lst.Add(p);
                    if (p.IsID)
                    {
                        Flags &= ~EClassFlag.AUTO_ID;
                        //if (p.GenType.GTName != "int")
                        //    throw new Exception(string.Format( "ID need to be defined as int.({0})", table.TableName));
                    }
                }
                p.AddVariant(variant);
            }
            if (AutoId)
                lst.Insert(0, new Property("id", Factory.GetInstance().GetGenType("int")));
            m_Properties = lst.ToArray();
            for (int i = 0; i < m_Properties.Length; i++)
            {
                m_Properties[i].Index = i;
                if (m_Properties[i].IsID)
                {
                    IdIndex = i;
                    IsIdDefined = true;
                }
            }
        }

        public int PropertyCount { get { return m_Properties == null ? 0 : m_Properties.Length; } }

        public Property GetProperty(int index)
        {
            return m_Properties[index];
        }

        public void IgnoreWithPattern(string pattern)
        {
            if (string.IsNullOrEmpty(pattern))
            {
                foreach (var p in m_Properties)
                {
                    p.Ignore = false;
                }
            }
            else
            {
                foreach (var p in m_Properties)
                {
                    p.Ignore = Regex.IsMatch(p.Name, pattern);
                }
            }
        }

        public int IndexOfProperty(string pname)
        {
            if (m_Properties == null)
                return -1;
            for (int i = 0; i < m_Properties.Length; i++)
            {
                if (m_Properties[i].Name == pname)
                    return i;
            }
            return -1;
        }

        public bool GetProperty(string pname, out Property property)
        {
            for (int i = PropertyCount - 1; i >= 0; i--)
            {
                Property p = m_Properties[i];
                if (p.Name == pname)
                {
                    property = p;
                    return true;
                }
            }
            property = default(Property);
            return false;
        }

        public Property[] Properties { get { return m_Properties; } }

        public string GetFormatString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("BEGIN:").Append(ClassName).Append("\n");
            int num = 0;
            for (int i = 0; i < PropertyCount; i++)
            {
                int len = m_Properties[i].Name.Length;
                if (len > num)
                    num = len;
            }
            for (int i = 0; i < PropertyCount; i++)
            {
                Property p = m_Properties[i];
                builder.Append("    ");
                builder.Append(p.Name);
                for (int e = num + 2 - p.Name.Length; e > 0; e--)
                    builder.Append(' ');
                builder.Append(":").Append(p.GenType.Name);
                builder.Append("\n");
            }
            builder.Append("End:").Append(ClassName);
            return builder.ToString();
        }
    }
}
